/**
 * @file
 * @brief Asynchronous queue
 */

#ifndef _ASYNC_QUEUE_H
#define _ASYNC_QUEUE_H

#include <types.h>
#include <list.h>
#include <pthread.h>

/** @brief Asynchronous queue structure */
struct async_queue {
    struct list_head head;
    pthread_mutex_t mutex;
    pthread_cond_t cond;
    int waiter_n;
    bool interrupted;
};

/** @brief Static initializer of \c async_queue */
#define ASYNC_QUEUE_INITIALIZER(name) { LIST_HEAD_INIT(name.head), \
    PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER, 0, false}

/** @brief Initialize \c async_queue */
extern void async_queue_init(struct async_queue *queue);
/** @brief Free \c async_queue */
extern void async_queue_free(struct async_queue *queue);
/** @brief Push data into \c async_queue */
extern void async_queue_push(struct async_queue *queue, void *data);
/**
 * @brief Pop data from \c async_queue
 *
 * This functions blocks until \p queue has data to pop.
 * @param queue  \c async_queue to pop data
 * @return Data or NULL if interrupted
 */
extern void *async_queue_pop(struct async_queue *queue);
/**
 * @brief Pop data from \c async_queue with a specified timeout value
 * @param queue  \c async_queue to pop data
 * @param timeout  Timeout in milliseconds
 * @return Data or NULL if interrupted or timeout
 */
extern void *async_queue_timeout_pop(struct async_queue *queue, const struct timespec *timeout);
/**
 * @brief Interrupt \c async_queue
 *
 * This causes all \c async_queue_pop() or \c async_queue_timeout_pop()
 * currently blocking on \p queue to return NULL immediately. If there are
 * no blocking pop functions, the interrupt flag pends until the first call
 * of pop function, causes it to return NULL and clear the interrupt flag.
 *
 * @param queue  \c async_queue to interrupt
 *
 * @note In detail of implementation, this function turns on the interrupt
 * flags and wake up all blocking pop functions. The interrupt flag is turned
 * off again inside the last unblocked pop function, i.e. when number of
 * waiters equals zero. It is possible that some pop function inside endless
 * loop increases the number of waiters and causes the number of waiters to
 * never be zero; hence, the interrupt flag never be clear. This scenario
 * won't occur in syncup program, so we don't add extra overhead to solve
 * this problem. Be careful when you use these codes in other places.
 */
extern void async_queue_interrupt(struct async_queue *queue);
/**
 * @brief Acquire the lock of \p queue
 *
 * After that, you can only use <code>async_queue_*_unlocked()</code> function
 * variants on \p queue. Otherwise it will deadlock.
 * @param queue  \c async_queue to lock
 */
extern void async_queue_lock(struct async_queue *queue);
/**
 * @brief Release the lock of \p queue
 * @param queue  \c async_queue to unlock
 */
extern void async_queue_unlock(struct async_queue *queue);
extern void async_queue_push_unlocked(struct async_queue *queue, void *data);
extern void *async_queue_pop_unlocked(struct async_queue *queue);
extern void *async_queue_timeout_pop_unlocked(struct async_queue *queue, const struct timespec *timeout);
extern void async_queue_interrupt_unlocked(struct async_queue *queue);

#endif /* ! _ASYNC_QUEUE_H */
